package art_of_java_concurrent_programming.double_check_and_lazy_init;

/**
 * 延迟初始化
 * @author lixiangke
 * @date 2018/1/22
 */
public class Singleton {

    private static volatile Singleton singleton;

    private Singleton() {}

    /**
     * 效率太低, 每次调用 getInstance 都要锁
     * @return
     */
    public static synchronized Singleton getInstance() {
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }

    /**
     * 双重检查加锁
     */
    public static Singleton getInstance2() {
        if (singleton == null) {
            synchronized (Singleton.class) {
                // 第二次检查, 如果不检查, 可能导致 Singleton 多次初始化
                if (singleton == null) {
                    // 此行代码可以分解为 3 步
                    // 1：分配对象的内存空间
                    // 2：初始化对象
                    // 3：设置instance指向刚分配的内存地址
                    // 由于 2 和 3 可能发生重排序, 可能会导致线程A执行了步骤3, 还未执行步骤2
                    // 然后线程B获得了未初始化的Singleton对象
                    // 要解决此问题, 将 Singleton用 volatile 修饰即可, 需要jdk5以上
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }

    /**
     * 根据Java语言规范，在首次发生下列任意一种情况时，一个类或接口类型T将被立即初始化。
     * 1）T是一个类，而且一个T类型的实例被创建。
     * 2）T是一个类，且T中声明的一个静态方法被调用。
     * 3）T中声明的一个静态字段被赋值。
     * 4）T中声明的一个静态字段被使用，而且这个字段不是一个常量字段。
     * 5）T是一个顶级类（Top Level Class，见Java语言规范的§7.6），而且一个断言语句嵌套在T内部被执行。
     *
     * 首次执行getInstance3()方法的线程将导致Holder类被初始化（符合情况4）
     *
     * JVM在类的初始化阶段（即在Class被加载后，且被线程使用之前），会执行类的初始化。
     * 在执行类的初始化期间，JVM会去获取一个锁。这个锁可以同步多个线程对同一个类的初始化。
     * @return
     */
    public static Singleton getInstance3() {
        return Holder.singleton;
    }

    private static class Holder {
        public static Singleton singleton = new Singleton();
    }

}
